<?php

namespace app;

use app\core\Log;

use PhpParser\BuilderFactory;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinter;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp\LogicalAnd;
use PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\BinaryOp\Equal;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Expr\BinaryOp\NotEqual;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;

use PhpParser\Node\Stmt\Label;
use PhpParser\Node\Identifier;
use PhpParser\Node\Expr\ArrayDimFetch;

use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;

use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;



/**
 * 使用 PhpParser 库构建 PHP 代码
 */
class PluginPhp
{
    protected $factory;
    protected $class;
    protected $methods = [];
    protected $property = [];

    protected $stmtsList = [];

    /**
     * 构造一个新的 ClassBuilder 实例。
     */
    public function __construct()
    {
        $this->factory = new BuilderFactory();
    }

    /**
     * 添加具有指定类名的类。
     *
     * @param string $className 类名。
     * @return $this
     */
    public function addClass($className)
    {
        $this->class = $this->factory->class($className);
        return $this;
    }


    /**
     * 添加属性
     * @param mixed $varName
     * @param mixed $array
     * @param mixed $property
     */
    public function addPropertyStatement($varName, $array, $property = Node\Stmt\Class_::MODIFIER_PUBLIC)
    {

        $result = [];
        foreach ($array as $key => $value) {
            $result[] = new ArrayItem(new Node\Scalar\String_($key), new Node\Scalar\String_($value));
        }

        $arrayNode = new Array_($result);
        $property = new PropertyProperty($varName, $arrayNode);
        $propertyStmt = new Property(
            Node\Stmt\Class_::MODIFIER_PUBLIC,
            [$property]
        );
        $this->property[] = $propertyStmt;
        return $propertyStmt;



    }


    public function addUseStatement($use, $type = Use_::TYPE_NORMAL)
    {

        $result = [];
        if (is_string($use)) {
            $result[] = new UseUse(new Name($use));

            $useNode = new Use_($result, $type);

            $this->stmtsList[] = $useNode;



        }




    }

    /**
     * 添加具有指定方法名的方法。
     *
     * @param string $methodName 方法名。
     * @return MethodBuilder
     */
    public function addMethod($methodName)
    {
        $methodBuilder = new MethodBuilder($methodName);
        $this->methods[] = $methodBuilder;
        return $methodBuilder;
    }

    /**
     * 生成构建类的 PHP 代码。
     *
     * @return string 生成的 PHP 代码。
     */
    public function getCode()
    {
        $result = [];
        foreach ($this->stmtsList as $stmt) {
            $result[] = $stmt;
        }


        foreach ($this->methods as $methodBuilder) {
            $method = $this->factory->method($methodBuilder->getName())->makePublic();
            foreach ($methodBuilder->getStmts() as $stmt) {
                $method->addStmt($stmt);
            }
            $this->class->addStmt($method);
        }

        $stmts = $this->class->getNode();
        foreach (array_reverse($this->property) as $property) {
            array_unshift($stmts->stmts, $property);

            //print_r($property);
        }

        $result[] = $stmts;

        $prettyPrinter = new PrettyPrinter\Standard();
        return $prettyPrinter->prettyPrintFile($result);
    }

}

class MethodBuilder
{
    /**
     * 方法名
     *
     * @var string
     */
    private $name;

    /**
     * 语句列表
     *
     * @var array
     */
    public $stmts = [];

    /**
     * 构造方法
     *
     * @param string $name 方法名
     */
    public function __construct($name)
    {
        $this->name = $name;
        $this->IfStatement = false;
        $this->tempStmt = [];
    }

    /**
     * 获取方法名
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 添加语句
     *
     * @param mixed $node 语句节点
     * @return $this
     */
    public function addStmt($node = false)
    {
        if (empty($node)) {
            $node = $this->tempStmt;
            $this->tempStmt = "";
        }
        // 如果传入的是一个表达式，将其包装在一个表达式语句中
        if ($node instanceof PhpParser\Node\Expr) {
            $node = new Expression($node);
        }
        //        print_r($node);
        $this->stmts[] = $node;
        return $this;
    }

    /**
     * 添加字符串连接赋值语句
     *
     * @param string $target 目标变量名
     * @param mixed $value 值
     * @return mixed
     */
    public function addStringConcatAssignment($target, $value, $concat = false)
    {
        // 判断是否为字符串连接操作
        $isConcatenation = strpos($target, '.') === 0;
        if ($isConcatenation) {
            $target = substr($target, 1);
        }

        if (strpos($target, '$this->') === 0) {
            $variable = new PropertyFetch(new Variable('this'), substr($target, 7));
        } else {
            $variable = new Variable(ltrim($target, '$'));
        }

        if (is_object($value)) {
            $valueNode = $value;
        } elseif (is_array($value)) {
            throw new \Exception('尝试给字符串连接赋值，传入参数 $value 不是字符串');
        } else {
            if (is_string($value)) {
                $valueNode = new String_($value);
            }
            ///echo "输出结果；{$value}";
        }

        if ($concat) {
            $assignExpr = new \PhpParser\Node\Expr\AssignOp\Concat(
                $variable,
                $valueNode
            );
        } else {
            $assignExpr = new Assign($variable, $valueNode);
        }



        $AssignNode = new Expression($assignExpr);

        //print_r($AssignNode);
        $this->tempStmt = $AssignNode;
        return $AssignNode;
    }
    /*    public function addStringConcatAssignment($target, $value)
       {
           // 判断是否为字符串连接操作
           $isConcatenation = strpos($target, '.') === 0;
           if ($isConcatenation) {
               $target = substr($target, 1);
           }

           if (strpos($target, '$this->') === 0) {
               $variable = new PropertyFetch(new Variable('this'), substr($target, 7));
           } else {
               $variable = new Variable(ltrim($target, '$'));
           }


           //echo "target: ";
           //print_r($value);
           if (is_object($value)) {
               $valueNode = $value;
           } elseif (is_array($value)) {
               print_r($value);
               throw new \Exception('尝试给字符串赋值，传入参数 $value 是数组');
               return;
           } else {
               // 如果不是函数调用，构造值节点
               if (is_string($value)) {
                   $valueNode = new String_($value);
               } elseif (is_numeric($value)) {
                   $valueNode = new \PhpParser\Node\Scalar\LNumber($value);
               } elseif (is_array($value)) {
                   $arrayItems = array_map(function ($item) {
                       return new ArrayItem(new String_($item));
                   }, $value);
                   $valueNode = new Array_($arrayItems);
               } else {
                   $valueNode = new String_((string) $value);
               }
           }

           // 创建赋值表达式
           if ($isConcatenation) {
               $concatExpr = new Concat($variable, $valueNode);
               $assignExpr = new Assign($variable, $concatExpr);
           } else {
               //$assignExpr = new Assign($variable, $valueNode);
               $assignExpr = $valueNode;
           }

           $AssignNode = new Expression($assignExpr);
           $this->tempStmt = $AssignNode;
           return $AssignNode;
       } */




    /**
     * 添加将给定变量赋值给$this对象的属性的语句
     *
     * @param string $propertyName 属性名
     * @param string $variableName 变量名
     * @return $this
     */
    public function addPropertyAssignment($propertyName, $variableName)
    {
        $this->stmts[] = new Expression(
            new Assign(
                new PropertyFetch(new Variable('this'), $propertyName),
                new Variable($variableName)
            )
        );
        return $this;
    }

    /**
     * 添加返回语句
     *
     * @param mixed $value 要返回的值
     * @return $this
     */
    public function addReturnStatement($value)
    {
        if (is_string($value)) {
            if (strpos($value, 'this->') === 0) {
                // 返回对象属性
                $propertyName = substr($value, strlen('this->'));
                $expr = new PropertyFetch(new Variable('this'), $propertyName);
            } elseif (strpos($value, '$') === 0) {
                // 返回变量
                $variableName = substr($value, 1);
                $expr = new Variable($variableName);
            } else {
                // 返回字符串字面量
                $expr = new String_($value);
            }
        } elseif (is_bool($value)) {
            $expr = new ConstFetch(new Name($value ? 'true' : 'false'));
        } elseif (is_numeric($value)) {
            $expr = is_float($value) ? new DNumber($value) : new PhpParser\Node\Scalar\LNumber($value);
        } else {
            // 默认情况，直接使用表达式
            $expr = $value;
        }

        $returnNode = new Return_($expr);
        $this->tempStmt = $returnNode;
        return $returnNode;
        /*
                if ($this->IfStatement) {
                    return $returnNode;
                } else {
                    $this->stmts[] = $returnNode;
                    return $this;
                }
        */
    }


    public function addFunctionStatement($value)
    {
        if (!isset($value['funArgs'])) {
            print_r($value);

        }
        $result = [];
        foreach ($value['funArgs'] as $key => $val) {
            //print_r($val);
            if ($val['type'] == 'concat') {
                $result[] = $this->addConcatStatement(array_reverse($val['code']));
            } else {
                $result[] = $this->analyse($val);
            }
        }
        //print_r($result);
        //$funName = new String_($value['funName']);
        $stmts = new Expr\StaticCall(
            new Node\Name('Route'),
            'callFun',
            [
                new String_($value['funName']),
                new Expr\Array_(
                    $result
                ),
                new String_($value['rowName'])
            ]
        );
        //print_r($stmts);
        return $stmts;
    }
    public function addConcatStatement($value)
    {
        if (count($value) === 1) {
            return $this->analyse($value[0]);
        }

        $left = $value[0];
        array_shift($value);
        $right = $this->addConcatStatement($value);
        return new Concat(
            $right,
            $this->analyse($left),
        );
    }

    public function addarrayStatement($value)
    {
        if (count($value) === 1) {
            return $this->analyse($value[0]);
        }

        $left = $value[0];
        array_shift($value);
        $right = $this->addarrayStatement($value);
        return new ArrayDimFetch(
            $right,
            $this->analyse($left),
        );
    }


    public function addJsonStatement($value)
    {
        //print_r($value);
        $concat = $this->addConcatStatement(array_reverse($value['code']['code']));
        $stmts = $this->addStringConcatAssignment('.$Message', $concat);

        return $stmts;
    }

    /**
     * 添加条件语句
     *
     * @param array $block 条件语句块
     * @return mixed
     */
    public function addIfStatement($block)
    {
        $condition = $this->buildCondition($block['cond']);
        if (!isset($block['ifStmts'])) {
            //print_r($block);
            return null;
        }
        $ifStmt = new If_(
            $condition,
            ['stmts' => $block['ifStmts']]
        );
        if (isset($block['elseIf'])) {
            foreach ($block['elseIf'] as $elseIfStmt) {
                //print_r($elseIfStmt);
                $elseifCondition = $this->buildCondition($elseIfStmt['cond']);
                $elseif = new ElseIf_(
                    $elseifCondition,
                    $elseIfStmt['elseIfStmts'][0]
                );
                //print_r($elseif);
                $ifStmt->elseifs[] = $elseif;
            }
        }
        if (isset($block['elseStmts'])) {
            $elseStmt = new Else_($block['elseStmts']);
            $ifStmt->else = $elseStmt;
        }
        //print_r($condition);
        return $ifStmt;
    }

    public function analyse($array)
    {
        if (is_string($array)) {
            throw new \Exception("解释器解析错误，要求传入数组，实际传入的是字符串");
        }
        //print_r($array);
        if ($array['type'] == 'string') {
            //print_r($array);
            if (empty($array['code']) || $array['code'] == 'null') {
                return new ConstFetch(new Name('null'));
            }
            return new String_($array['code']);
        }
        if ($array['type'] == 'var') {
            return new Expr\StaticCall(
                new Node\Name('Route'),
                'getVar',
                [
                    new String_($array['varName']),
                ]
            );
        }
        if ($array['type'] == 'fun') {
            return $this->addFunctionStatement($array);
        }
        if ($array['type'] == 'array') {

            $varName = [
                'type' => 'var',
                'varName' => $array['varName']
            ];
            $key = array_merge([$varName], $array['key']);
            //print_r($key);
            //print_r($result);
            return $this->addarrayStatement(array_reverse($key));
        }

        if ($array['type'] == 'goto') {
            return new Node\Stmt\Goto_($array['labelName']);
        }
        if ($array['type'] == 'label') {
            return new Label(new Identifier($array['labelName']));
        }

        if ($array['type'] == 'json') {
            //print_r($array);

            return $this->addJsonStatement($array);
            //return new Expr\ConstFetch(new Name\FullyQualified('string'));
        }
        if ($array['type'] == 'concat') {
            //print_r($array);

            return $this->addConcatStatement(array_reverse($array['code']));
            //return new Expr\ConstFetch(new Name\FullyQualified('string'));
        }
        if ($array['type'] == 'maths') {
            print_r($array);

            return new Expr\StaticCall(
                new Node\Name('MathExpression'),
                'evaluate',
                [
                    $this->analyse($array['code']),
                ]
            );
        }

        $info = debug_backtrace()[0];
        $error = [
            'type' => 'Warning',
            'line' => $info['line'],
            'path' => $info['file'],
            'message' => '翻译器解析错误，未定义的类型；' . $array['type']

        ];
        Log::log($error);
        throw new \Exception('翻译器解析错误，未定义的类型；' . $array['type']);
        //echo 1;
    }

    private function buildCondition($conditionArray)
    {
        if ($conditionArray['type'] === 'binary') {
            $left = $this->buildCondition($conditionArray['left']);
            $right = $this->buildCondition($conditionArray['right']);
            switch ($conditionArray['operator']) {
                case '&&':
                    return new Expr\BinaryOp\LogicalAnd($left, $right);
                case '||':
                    return new Expr\BinaryOp\LogicalOr($left, $right);
                case '&':
                    return new Expr\BinaryOp\BitwiseAnd($left, $right);
                case '|':
                    return new Expr\BinaryOp\BitwiseOr($left, $right);
                case '^':
                    return new Expr\BinaryOp\BitwiseXor($left, $right);
                default:
                    throw new \Exception("Unsupported binary operator: {$conditionArray['operator']}");
            }
        } elseif ($conditionArray['type'] === 'comparison') {
            //print_r($conditionArray);
            $left = $this->buildCondition($conditionArray['left']);
            $right = $this->buildCondition($conditionArray['right']);
            switch ($conditionArray['operator']) {
                case '!=':
                    return new Expr\BinaryOp\NotEqual($left, $right);
                case '!==':
                    return new Expr\BinaryOp\NotIdentical($left, $right);
                case '==':
                    return new Expr\BinaryOp\Equal($left, $right);
                case '===':
                    return new Expr\BinaryOp\Identical($left, $right);
                case '<':
                    return new Expr\BinaryOp\Smaller($left, $right);
                case '>':
                    return new Expr\BinaryOp\Greater($left, $right);
                case '<=':
                    return new Expr\BinaryOp\SmallerOrEqual($left, $right);
                case '>=':
                    return new Expr\BinaryOp\GreaterOrEqual($left, $right);
                default:
                    $info = debug_backtrace();

                    print_r($conditionArray);

                    //print_r($info);
                    $error = [
                        'type' => 'Notice',
                        'line' => 0,
                        'path' => $dicSavePath,
                        'message' => "不支持的比较操作符: {$conditionArray['operator']}",
                    ];
                    Log::log($error);
                    throw new \Exception("Unsupported comparison operator: {$conditionArray['operator']}");
            }
            /*         } elseif ($conditionArray['type'] === 'var') {
                        return new Expr\Variable($conditionArray['varName']);
                    } elseif ($conditionArray['type'] === 'string') {
                        return new Scalar\String_($conditionArray['code']); */
        } else {
            return $this->analyse($conditionArray);
            //throw new \Exception("条件语句未定义的类型: {$conditionArray['type']}");
        }
    }


    /**
     * 获取方法节点的所有语句节点
     *
     * @return array
     */
    public function getStmts()
    {
        return $this->stmts;
    }

}
